package org.ssssssss.magicapi.datasource.service;

import net.hky365.shop.framework.common.util.CommonUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.context.event.EventListener;
import org.springframework.util.ClassUtils;
import org.ssssssss.magicapi.core.config.Constants;
import org.ssssssss.magicapi.core.event.FileEvent;
import org.ssssssss.magicapi.core.service.AbstractMagicDynamicRegistry;
import org.ssssssss.magicapi.core.service.MagicResourceStorage;
import org.ssssssss.magicapi.datasource.model.DataSourceInfo;
import org.ssssssss.magicapi.datasource.model.MagicDynamicDataSource;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class DataSourceMagicDynamicRegistry extends AbstractMagicDynamicRegistry<DataSourceInfo> {

    private final MagicDynamicDataSource magicDynamicDataSource;

    private static final Logger logger = LoggerFactory.getLogger(DataSourceMagicDynamicRegistry.class);

    private static final ClassLoader CLASSLOADER = DataSourceMagicDynamicRegistry.class.getClassLoader();

    // copy from DataSourceBuilder
    private static final String[] DATA_SOURCE_TYPE_NAMES = new String[]{
            "com.zaxxer.hikari.HikariDataSource",
            "org.apache.tomcat.jdbc.pool.DataSource",
            "org.apache.commons.dbcp2.BasicDataSource"};

    public DataSourceMagicDynamicRegistry(MagicResourceStorage<DataSourceInfo> magicResourceStorage, MagicDynamicDataSource magicDynamicDataSource) {
        super(magicResourceStorage);
        this.magicDynamicDataSource = magicDynamicDataSource;
    }

    @EventListener(condition = "#item.type == 'datasource'")
    public void onFileEvent(FileEvent item) {
        if (!CommonUtils.equals(item.getType(), "datasource")) {
            return;
        }
        try {
            processEvent(item);
        } catch (Exception e) {
            logger.error("注册数据源失败", e);
        }
    }

    @Override
    protected boolean register(MappingNode<DataSourceInfo> mappingNode) {
        DataSourceInfo info = mappingNode.getEntity();
        Map<String, Object> properties = new HashMap<>(info.getProperties());
        if (Constants.ES_DRIVER.equals(info.getDriverClassName())) {
            properties.put("defaultReadOnly", true);
        }
        properties.put("url", info.getUrl());
        properties.put("username", info.getUsername());
        properties.put("password", info.getPassword());
        if (StringUtils.isBlank(info.getDriverClassName())) {
            String driverClass = DatabaseDriver.fromJdbcUrl(info.getUrl()).getDriverClassName();
            properties.put("driverClassName", driverClass);
        } else {
            properties.put("driverClassName", info.getDriverClassName());
        }
        DataSource datasource = createDataSource(getDataSourceType(info.getType()), properties);
        magicDynamicDataSource.put(info.getId(), info.getKey(), info.getName(), datasource, info.getMaxRows());
        return true;
    }

    @Override
    protected void unregister(MappingNode<DataSourceInfo> mappingNode) {
        magicDynamicDataSource.delete(mappingNode.getMappingKey());
    }

    // copy from DataSourceBuilder
    private DataSource createDataSource(Class<? extends DataSource> dataSourceType, Map<String, Object> properties) {
        DataSource dataSource = BeanUtils.instantiateClass(dataSourceType);
        ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
        ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
        aliases.addAliases("url", "jdbc-url");
        aliases.addAliases("username", "user");
        Binder binder = new Binder(source.withAliases(aliases));
        binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(dataSource));
        return dataSource;
    }

    @SuppressWarnings("unchecked")
    private Class<? extends DataSource> getDataSourceType(String datasourceType) {
        if (StringUtils.isNotBlank(datasourceType)) {
            try {
                return (Class<? extends DataSource>) ClassUtils.forName(datasourceType, CLASSLOADER);
            } catch (Exception ignored) {

            }
        }
        for (String name : DATA_SOURCE_TYPE_NAMES) {
            try {
                return (Class<? extends DataSource>) ClassUtils.forName(name, CLASSLOADER);
            } catch (Exception ignored) {
                // ignored
            }
        }
        return null;
    }

    @Override
    public List<DataSourceInfo> defaultMappings() {
        return magicDynamicDataSource.datasourceNodes().stream().filter(it -> it.getId() == null).map(it -> {
            DataSourceInfo dataSourceInfo = new DataSourceInfo();
            dataSourceInfo.setName(StringUtils.defaultIfBlank(it.getName(), StringUtils.defaultIfBlank(it.getKey(), "默认数据源")));
            dataSourceInfo.setKey(it.getKey());
            return dataSourceInfo;
        }).collect(Collectors.toList());
    }
}
